{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "3HqK14JLSStR"
   },
   "source": [
    "# CS224N Assignment 1: Exploring Word Vectors (25 Points)\n",
    "### <font color='blue'> Due 4:30pm, Tue April 9th 2024</font>\n",
    "\n",
    "Welcome to CS224N! \n",
    "\n",
    "Before you start, make sure you **read the README.md** in the same directory as this notebook for important setup information. You need to install some Python libraries before you can successfully do this assignment. A lot of code is provided in this notebook, and we highly encourage you to read and understand it as part of the learning :)\n",
    "\n",
    "If you aren't super familiar with Python, Numpy, or Matplotlib, we recommend you check out the review session on Friday. The session will be recorded and the material will be made available on our [website](http://web.stanford.edu/class/cs224n/index.html#schedule). The CS231N Python/Numpy [tutorial](https://cs231n.github.io/python-numpy-tutorial/) is also a great resource.\n",
    "\n",
    "\n",
    "**Assignment Notes:** Please make sure to save the notebook as you go along. Submission Instructions are located at the bottom of the notebook."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-03-27T08:04:19.343709Z",
     "start_time": "2024-03-27T08:04:15.222676Z"
    },
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "8AGQxROrSStf",
    "outputId": "45390a52-8c82-466d-dcf3-d5b355f14aa4"
   },
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "a7fa9f92bb3e4f14a41e3ca289629e95",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Downloading readme:   0%|          | 0.00/7.81k [00:00<?, ?B/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "bedf8d44e9834afcadb4a9519f2a4deb",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Downloading data:   0%|          | 0.00/21.0M [00:00<?, ?B/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "4eaf78248bac437498017fcad6e01e8b",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Downloading data:   0%|          | 0.00/20.5M [00:00<?, ?B/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "bf0e2a33cda44b039f978370a987aff9",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Downloading data:   0%|          | 0.00/42.0M [00:00<?, ?B/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "275096dbd59e47c2a0955aebd481edea",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Generating train split:   0%|          | 0/25000 [00:00<?, ? examples/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "3573591e5fd04a2c86053274a359e90d",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Generating test split:   0%|          | 0/25000 [00:00<?, ? examples/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "1b797c58c65242b5b54d85ea618715ac",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Generating unsupervised split:   0%|          | 0/50000 [00:00<?, ? examples/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# All Import Statements Defined Here\n",
    "# Note: Do not add to this list.\n",
    "# ----------------\n",
    "\n",
    "import sys\n",
    "assert sys.version_info[0] == 3\n",
    "assert sys.version_info[1] >= 8\n",
    "\n",
    "from platform import python_version\n",
    "assert int(python_version().split(\".\")[1]) >= 5, \"Please upgrade your Python version following the instructions in \\\n",
    "    the README.md file found in the same directory as this notebook. Your Python version is \" + python_version()\n",
    "\n",
    "from gensim.models import KeyedVectors\n",
    "from gensim.test.utils import datapath\n",
    "import pprint\n",
    "import matplotlib.pyplot as plt\n",
    "plt.rcParams['figure.figsize'] = [10, 5]\n",
    "\n",
    "from datasets import load_dataset\n",
    "imdb_dataset = load_dataset(\"stanfordnlp/imdb\")\n",
    "\n",
    "import re\n",
    "import numpy as np\n",
    "import random\n",
    "import scipy as sp\n",
    "from sklearn.decomposition import TruncatedSVD\n",
    "from sklearn.decomposition import PCA\n",
    "\n",
    "START_TOKEN = '<START>'\n",
    "END_TOKEN = '<END>'\n",
    "NUM_SAMPLES = 150\n",
    "\n",
    "np.random.seed(0)\n",
    "random.seed(0)\n",
    "# ----------------"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "SICd5IedSSto"
   },
   "source": [
    "## Word Vectors\n",
    "\n",
    "Word Vectors are often used as a fundamental component for downstream NLP tasks, e.g. question answering, text generation, translation, etc., so it is important to build some intuitions as to their strengths and weaknesses. Here, you will explore two types of word vectors: those derived from *co-occurrence matrices*, and those derived via *GloVe*. \n",
    "\n",
    "**Note on Terminology:** The terms \"word vectors\" and \"word embeddings\" are often used interchangeably. The term \"embedding\" refers to the fact that we are encoding aspects of a word's meaning in a lower dimensional space. As [Wikipedia](https://en.wikipedia.org/wiki/Word_embedding) states, \"*conceptually it involves a mathematical embedding from a space with one dimension per word to a continuous vector space with a much lower dimension*\"."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "6Eug6AVrSStr"
   },
   "source": [
    "## Part 1: Count-Based Word Vectors (10 points)\n",
    "\n",
    "Most word vector models start from the following idea:\n",
    "\n",
    "*You shall know a word by the company it keeps ([Firth, J. R. 1957:11](https://en.wikipedia.org/wiki/John_Rupert_Firth))*\n",
    "\n",
    "Many word vector implementations are driven by the idea that similar words, i.e., (near) synonyms, will be used in similar contexts. As a result, similar words will often be spoken or written along with a shared subset of words, i.e., contexts. By examining these contexts, we can try to develop embeddings for our words. With this intuition in mind, many \"old school\" approaches to constructing word vectors relied on word counts. Here we elaborate upon one of those strategies, *co-occurrence matrices* (for more information, see [here](https://web.stanford.edu/~jurafsky/slp3/6.pdf) or [here](https://web.archive.org/web/20190530091127/https://medium.com/data-science-group-iitr/word-embedding-2d05d270b285))."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "L6uQ9-DVSSts"
   },
   "source": [
    "### Co-Occurrence\n",
    "\n",
    "A co-occurrence matrix counts how often things co-occur in some environment. Given some word $w_i$ occurring in the document, we consider the *context window* surrounding $w_i$. Supposing our fixed window size is $n$, then this is the $n$ preceding and $n$ subsequent words in that document, i.e. words $w_{i-n} \\dots w_{i-1}$ and $w_{i+1} \\dots w_{i+n}$. We build a *co-occurrence matrix* $M$, which is a symmetric word-by-word matrix in which $M_{ij}$ is the number of times $w_j$ appears inside $w_i$'s window among all documents.\n",
    "\n",
    "**Example: Co-Occurrence with Fixed Window of n=1**:\n",
    "\n",
    "Document 1: \"all that glitters is not gold\"\n",
    "\n",
    "Document 2: \"all is well that ends well\"\n",
    "\n",
    "\n",
    "|     *    | `<START>` | all | that | glitters | is   | not  | gold  | well | ends | `<END>` |\n",
    "|----------|-------|-----|------|----------|------|------|-------|------|------|-----|\n",
    "| `<START>`    | 0     | 2   | 0    | 0        | 0    | 0    | 0     | 0    | 0    | 0   |\n",
    "| all      | 2     | 0   | 1    | 0        | 1    | 0    | 0     | 0    | 0    | 0   |\n",
    "| that     | 0     | 1   | 0    | 1        | 0    | 0    | 0     | 1    | 1    | 0   |\n",
    "| glitters | 0     | 0   | 1    | 0        | 1    | 0    | 0     | 0    | 0    | 0   |\n",
    "| is       | 0     | 1   | 0    | 1        | 0    | 1    | 0     | 1    | 0    | 0   |\n",
    "| not      | 0     | 0   | 0    | 0        | 1    | 0    | 1     | 0    | 0    | 0   |\n",
    "| gold     | 0     | 0   | 0    | 0        | 0    | 1    | 0     | 0    | 0    | 1   |\n",
    "| well     | 0     | 0   | 1    | 0        | 1    | 0    | 0     | 0    | 1    | 1   |\n",
    "| ends     | 0     | 0   | 1    | 0        | 0    | 0    | 0     | 1    | 0    | 0   |\n",
    "| `<END>`      | 0     | 0   | 0    | 0        | 0    | 0    | 1     | 1    | 0    | 0   |\n",
    "\n",
    "In NLP, we commonly use `<START>` and `<END>` tokens to mark the beginning and end of sentences, paragraphs, or documents. These tokens are included in co-occurrence counts, encapsulating each document, for example: \"`<START>` All that glitters is not gold `<END>`\".\n",
    "\n",
    "The matrix rows (or columns) provide word vectors based on word-word co-occurrence, but they can be large. To reduce dimensionality, we employ Singular Value Decomposition (SVD), akin to PCA, selecting the top $k$ principal components. The SVD process decomposes the co-occurrence matrix $A$ into singular values in the diagonal $S$ matrix and new, shorter word vectors in $U_k$.\n",
    "\n",
    "This dimensionality reduction maintains semantic relationships; for instance, *doctor* and *hospital* will be closer than *doctor* and *dog*.\n",
    "\n",
    "For those unfamiliar with eigenvalues and SVD, a beginner-friendly introduction to SVD is available [here](https://davetang.org/file/Singular_Value_Decomposition_Tutorial.pdf). Additional resources for in-depth understanding include lectures [7](https://web.stanford.edu/class/cs168/l/l7.pdf), [8](http://theory.stanford.edu/~tim/s15/l/l8.pdf), and [9](https://web.stanford.edu/class/cs168/l/l9.pdf) of CS168, providing high-level treatment of these algorithms. For practical implementation, utilizing pre-programmed functions from Python packages like numpy, scipy, or sklearn is recommended. While applying full SVD to large corpora can be memory-intensive, scalable techniques such as Truncated SVD exist for extracting the top $k$ vector components efficiently."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "7IKeK4xtSStv"
   },
   "source": [
    "### Plotting Co-Occurrence Word Embeddings\n",
    "\n",
    "Here, we will be using the Large Movie Review Dataset. This is a dataset for binary sentiment classification containing substantially more data than previous benchmark datasets. We provide a set of 25,000 highly polar movie reviews for training, and 25,000 for testing. There is additional unlabeled data for use as well. We provide a `read_corpus` function below that pulls out the text of a movie review from the dataset. The function also adds `<START>` and `<END>` tokens to each of the documents, and lowercases words. You do **not** have to perform any other kind of pre-processing."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-03-27T08:06:28.097673Z",
     "start_time": "2024-03-27T08:06:28.094138Z"
    },
    "id": "xwD2htUoSStw"
   },
   "outputs": [],
   "source": [
    "def read_corpus():\n",
    "    \"\"\" Read files from the Large Movie Review Dataset.\n",
    "        Params:\n",
    "            category (string): category name\n",
    "        Return:\n",
    "            list of lists, with words from each of the processed files\n",
    "    \"\"\"\n",
    "    files = imdb_dataset[\"train\"][\"text\"][:NUM_SAMPLES]\n",
    "    return [[START_TOKEN] + [re.sub(r'[^\\w]', '', w.lower()) for w in f.split(\" \")] + [END_TOKEN] for f in files]\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "hVLquFhjSStx"
   },
   "source": [
    "Let's have a look what these documents are like…."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-03-27T08:06:29.881790Z",
     "start_time": "2024-03-27T08:06:29.404708Z"
    },
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "mC7B9Cb-SSty",
    "outputId": "a1861c4f-723a-4d99-98d9-5f5db3a92e7a"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[['<START>', 'i', 'rented', 'i', 'am', 'curiousyellow', 'from', 'my', 'video', 'store', 'because',\n",
      "  'of', 'all', 'the', 'controversy', 'that', 'surrounded', 'it', 'when', 'it', 'was', 'first',\n",
      "  'released', 'in', '1967', 'i', 'also', 'heard', 'that', 'at', 'first', 'it', 'was', 'seized',\n",
      "  'by', 'us', 'customs', 'if', 'it', 'ever', 'tried', 'to', 'enter', 'this', 'country', 'therefore',\n",
      "  'being', 'a', 'fan', 'of', 'films', 'considered', 'controversial', 'i', 'really', 'had', 'to',\n",
      "  'see', 'this', 'for', 'myselfbr', 'br', 'the', 'plot', 'is', 'centered', 'around', 'a', 'young',\n",
      "  'swedish', 'drama', 'student', 'named', 'lena', 'who', 'wants', 'to', 'learn', 'everything',\n",
      "  'she', 'can', 'about', 'life', 'in', 'particular', 'she', 'wants', 'to', 'focus', 'her',\n",
      "  'attentions', 'to', 'making', 'some', 'sort', 'of', 'documentary', 'on', 'what', 'the', 'average',\n",
      "  'swede', 'thought', 'about', 'certain', 'political', 'issues', 'such', 'as', 'the', 'vietnam',\n",
      "  'war', 'and', 'race', 'issues', 'in', 'the', 'united', 'states', 'in', 'between', 'asking',\n",
      "  'politicians', 'and', 'ordinary', 'denizens', 'of', 'stockholm', 'about', 'their', 'opinions',\n",
      "  'on', 'politics', 'she', 'has', 'sex', 'with', 'her', 'drama', 'teacher', 'classmates', 'and',\n",
      "  'married', 'menbr', 'br', 'what', 'kills', 'me', 'about', 'i', 'am', 'curiousyellow', 'is',\n",
      "  'that', '40', 'years', 'ago', 'this', 'was', 'considered', 'pornographic', 'really', 'the', 'sex',\n",
      "  'and', 'nudity', 'scenes', 'are', 'few', 'and', 'far', 'between', 'even', 'then', 'its', 'not',\n",
      "  'shot', 'like', 'some', 'cheaply', 'made', 'porno', 'while', 'my', 'countrymen', 'mind', 'find',\n",
      "  'it', 'shocking', 'in', 'reality', 'sex', 'and', 'nudity', 'are', 'a', 'major', 'staple', 'in',\n",
      "  'swedish', 'cinema', 'even', 'ingmar', 'bergman', 'arguably', 'their', 'answer', 'to', 'good',\n",
      "  'old', 'boy', 'john', 'ford', 'had', 'sex', 'scenes', 'in', 'his', 'filmsbr', 'br', 'i', 'do',\n",
      "  'commend', 'the', 'filmmakers', 'for', 'the', 'fact', 'that', 'any', 'sex', 'shown', 'in', 'the',\n",
      "  'film', 'is', 'shown', 'for', 'artistic', 'purposes', 'rather', 'than', 'just', 'to', 'shock',\n",
      "  'people', 'and', 'make', 'money', 'to', 'be', 'shown', 'in', 'pornographic', 'theaters', 'in',\n",
      "  'america', 'i', 'am', 'curiousyellow', 'is', 'a', 'good', 'film', 'for', 'anyone', 'wanting',\n",
      "  'to', 'study', 'the', 'meat', 'and', 'potatoes', 'no', 'pun', 'intended', 'of', 'swedish',\n",
      "  'cinema', 'but', 'really', 'this', 'film', 'doesnt', 'have', 'much', 'of', 'a', 'plot', '<END>'],\n",
      " ['<START>', 'i', 'am', 'curious', 'yellow', 'is', 'a', 'risible', 'and', 'pretentious', 'steaming',\n",
      "  'pile', 'it', 'doesnt', 'matter', 'what', 'ones', 'political', 'views', 'are', 'because', 'this',\n",
      "  'film', 'can', 'hardly', 'be', 'taken', 'seriously', 'on', 'any', 'level', 'as', 'for', 'the',\n",
      "  'claim', 'that', 'frontal', 'male', 'nudity', 'is', 'an', 'automatic', 'nc17', 'that', 'isnt',\n",
      "  'true', 'ive', 'seen', 'rrated', 'films', 'with', 'male', 'nudity', 'granted', 'they', 'only',\n",
      "  'offer', 'some', 'fleeting', 'views', 'but', 'where', 'are', 'the', 'rrated', 'films', 'with',\n",
      "  'gaping', 'vulvas', 'and', 'flapping', 'labia', 'nowhere', 'because', 'they', 'dont', 'exist',\n",
      "  'the', 'same', 'goes', 'for', 'those', 'crappy', 'cable', 'shows', 'schlongs', 'swinging', 'in',\n",
      "  'the', 'breeze', 'but', 'not', 'a', 'clitoris', 'in', 'sight', 'and', 'those', 'pretentious',\n",
      "  'indie', 'movies', 'like', 'the', 'brown', 'bunny', 'in', 'which', 'were', 'treated', 'to', 'the',\n",
      "  'site', 'of', 'vincent', 'gallos', 'throbbing', 'johnson', 'but', 'not', 'a', 'trace', 'of',\n",
      "  'pink', 'visible', 'on', 'chloe', 'sevigny', 'before', 'crying', 'or', 'implying',\n",
      "  'doublestandard', 'in', 'matters', 'of', 'nudity', 'the', 'mentally', 'obtuse', 'should', 'take',\n",
      "  'into', 'account', 'one', 'unavoidably', 'obvious', 'anatomical', 'difference', 'between', 'men',\n",
      "  'and', 'women', 'there', 'are', 'no', 'genitals', 'on', 'display', 'when', 'actresses', 'appears',\n",
      "  'nude', 'and', 'the', 'same', 'cannot', 'be', 'said', 'for', 'a', 'man', 'in', 'fact', 'you',\n",
      "  'generally', 'wont', 'see', 'female', 'genitals', 'in', 'an', 'american', 'film', 'in',\n",
      "  'anything', 'short', 'of', 'porn', 'or', 'explicit', 'erotica', 'this', 'alleged',\n",
      "  'doublestandard', 'is', 'less', 'a', 'double', 'standard', 'than', 'an', 'admittedly',\n",
      "  'depressing', 'ability', 'to', 'come', 'to', 'terms', 'culturally', 'with', 'the', 'insides',\n",
      "  'of', 'womens', 'bodies', '<END>'],\n",
      " ['<START>', 'if', 'only', 'to', 'avoid', 'making', 'this', 'type', 'of', 'film', 'in', 'the',\n",
      "  'future', 'this', 'film', 'is', 'interesting', 'as', 'an', 'experiment', 'but', 'tells', 'no',\n",
      "  'cogent', 'storybr', 'br', 'one', 'might', 'feel', 'virtuous', 'for', 'sitting', 'thru', 'it',\n",
      "  'because', 'it', 'touches', 'on', 'so', 'many', 'important', 'issues', 'but', 'it', 'does', 'so',\n",
      "  'without', 'any', 'discernable', 'motive', 'the', 'viewer', 'comes', 'away', 'with', 'no', 'new',\n",
      "  'perspectives', 'unless', 'one', 'comes', 'up', 'with', 'one', 'while', 'ones', 'mind', 'wanders',\n",
      "  'as', 'it', 'will', 'invariably', 'do', 'during', 'this', 'pointless', 'filmbr', 'br', 'one',\n",
      "  'might', 'better', 'spend', 'ones', 'time', 'staring', 'out', 'a', 'window', 'at', 'a', 'tree',\n",
      "  'growingbr', 'br', '', '<END>']]\n",
      "corpus size:  290\n"
     ]
    }
   ],
   "source": [
    "imdb_corpus = read_corpus()\n",
    "pprint.pprint(imdb_corpus[:3], compact=True, width=100)\n",
    "print(\"corpus size: \", len(imdb_corpus[0]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "tfa216H1SSt0",
    "jp-MarkdownHeadingCollapsed": true
   },
   "source": [
    "### Question 1.1: Implement `distinct_words` [code] (2 points)\n",
    "\n",
    "Write a method to work out the distinct words (word types) that occur in the corpus.\n",
    "\n",
    "You can use `for` loops to process the input `corpus` (a list of list of strings), but try using Python list comprehensions (which are generally faster). In particular, [this](https://coderwall.com/p/rcmaea/flatten-a-list-of-lists-in-one-line-in-python) may be useful to flatten a list of lists. If you're not familiar with Python list comprehensions in general, here's [more information](https://python-3-patterns-idioms-test.readthedocs.io/en/latest/Comprehensions.html).\n",
    "\n",
    "Your returned `corpus_words` should be sorted. You can use python's `sorted` function for this.\n",
    "\n",
    "You may find it useful to use [Python sets](https://www.w3schools.com/python/python_sets.asp) to remove duplicate words."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "id": "NjJABbVFSSt1"
   },
   "outputs": [],
   "source": [
    "def distinct_words(corpus):\n",
    "    \"\"\" Determine a list of distinct words for the corpus.\n",
    "        Params:\n",
    "            corpus (list of list of strings): corpus of documents\n",
    "        Return:\n",
    "            corpus_words (list of strings): sorted list of distinct words across the corpus\n",
    "            n_corpus_words (integer): number of distinct words across the corpus\n",
    "    \"\"\"\n",
    "    corpus_words = []\n",
    "    n_corpus_words = -1\n",
    "    \n",
    "    # ------------------\n",
    "    # Write your implementation here.\n",
    "    wordset = set()\n",
    "    for document in corpus:\n",
    "        wordset.update(document)\n",
    "\n",
    "    corpus_words = sorted(list(wordset))\n",
    "    n_corpus_words = len(corpus_words)\n",
    "    \n",
    "    \n",
    "    # ------------------\n",
    "\n",
    "    return corpus_words, n_corpus_words"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "iKfXBXySSSt3",
    "outputId": "8b49421a-1cff-4f10-fe4a-927f30570b59"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--------------------------------------------------------------------------------\n",
      "Passed All Tests!\n",
      "--------------------------------------------------------------------------------\n"
     ]
    }
   ],
   "source": [
    "# ---------------------\n",
    "# Run this sanity check\n",
    "# Note that this not an exhaustive check for correctness.\n",
    "# ---------------------\n",
    "\n",
    "# Define toy corpus\n",
    "test_corpus = [\"{} All that glitters isn't gold {}\".format(START_TOKEN, END_TOKEN).split(\" \"), \"{} All's well that ends well {}\".format(START_TOKEN, END_TOKEN).split(\" \")]\n",
    "test_corpus_words, num_corpus_words = distinct_words(test_corpus)\n",
    "\n",
    "# Correct answers\n",
    "ans_test_corpus_words = sorted([START_TOKEN, \"All\", \"ends\", \"that\", \"gold\", \"All's\", \"glitters\", \"isn't\", \"well\", END_TOKEN])\n",
    "ans_num_corpus_words = len(ans_test_corpus_words)\n",
    "\n",
    "# Test correct number of words\n",
    "assert(num_corpus_words == ans_num_corpus_words), \"Incorrect number of distinct words. Correct: {}. Yours: {}\".format(ans_num_corpus_words, num_corpus_words)\n",
    "\n",
    "# Test correct words\n",
    "assert (test_corpus_words == ans_test_corpus_words), \"Incorrect corpus_words.\\nCorrect: {}\\nYours:   {}\".format(str(ans_test_corpus_words), str(test_corpus_words))\n",
    "\n",
    "# Print Success\n",
    "print (\"-\" * 80)\n",
    "print(\"Passed All Tests!\")\n",
    "print (\"-\" * 80)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "ymDFJn_lSSt5",
    "jp-MarkdownHeadingCollapsed": true
   },
   "source": [
    "### Question 1.2: Implement `compute_co_occurrence_matrix` [code] (3 points)\n",
    "\n",
    "Write a method that constructs a co-occurrence matrix for a certain window-size $n$ (with a default of 4), considering words $n$ before and $n$ after the word in the center of the window. Here, we start to use `numpy (np)` to represent vectors, matrices, and tensors. If you're not familiar with NumPy, there's a NumPy tutorial in the second half of this cs231n [Python NumPy tutorial](http://cs231n.github.io/python-numpy-tutorial/).\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "id": "v8MIy3KDSSt6"
   },
   "outputs": [],
   "source": [
    "def compute_co_occurrence_matrix(corpus, window_size=4):\n",
    "    \"\"\" Compute co-occurrence matrix for the given corpus and window_size (default of 4).\n",
    "    \n",
    "        Note: Each word in a document should be at the center of a window. Words near edges will have a smaller\n",
    "              number of co-occurring words.\n",
    "              \n",
    "              For example, if we take the document \"<START> All that glitters is not gold <END>\" with window size of 4,\n",
    "              \"All\" will co-occur with \"<START>\", \"that\", \"glitters\", \"is\", and \"not\".\n",
    "    \n",
    "        Params:\n",
    "            corpus (list of list of strings): corpus of documents\n",
    "            window_size (int): size of context window\n",
    "        Return:\n",
    "            M (a symmetric numpy matrix of shape (number of unique words in the corpus , number of unique words in the corpus)): \n",
    "                Co-occurence matrix of word counts. \n",
    "                The ordering of the words in the rows/columns should be the same as the ordering of the words given by the distinct_words function.\n",
    "            word2ind (dict): dictionary that maps word to index (i.e. row/column number) for matrix M.\n",
    "    \"\"\"\n",
    "    words, n_words = distinct_words(corpus)\n",
    "    M = np.zeros((n_words, n_words), dtype=float)\n",
    "    word2ind = {word:i for i, word in enumerate(words)}\n",
    "    \n",
    "    # ------------------\n",
    "    # Write your implementation here.\n",
    "    for document in corpus:\n",
    "        for i, word in enumerate(document):\n",
    "            word_idx = word2ind[word]\n",
    "            start = max(0, i - window_size)\n",
    "            end = min(len(document), i + 1 + window_size)\n",
    "            for j in range(start, end):\n",
    "                if i != j:\n",
    "                    context_word = document[j]\n",
    "                    context_index = word2ind[context_word]\n",
    "                    M[word_idx, context_index] += 1\n",
    "    # ------------------\n",
    "\n",
    "    return M, word2ind"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "u-6ID1qhSSt7",
    "outputId": "f4f1bccb-06a8-4bb6-ec80-f2b6201b2645"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--------------------------------------------------------------------------------\n",
      "Passed All Tests!\n",
      "--------------------------------------------------------------------------------\n"
     ]
    }
   ],
   "source": [
    "# ---------------------\n",
    "# Run this sanity check\n",
    "# Note that this is not an exhaustive check for correctness.\n",
    "# ---------------------\n",
    "\n",
    "# Define toy corpus and get student's co-occurrence matrix\n",
    "test_corpus = [\"{} All that glitters isn't gold {}\".format(START_TOKEN, END_TOKEN).split(\" \"), \"{} All's well that ends well {}\".format(START_TOKEN, END_TOKEN).split(\" \")]\n",
    "M_test, word2ind_test = compute_co_occurrence_matrix(test_corpus, window_size=1)\n",
    "\n",
    "# Correct M and word2ind\n",
    "M_test_ans = np.array( \n",
    "    [[0., 0., 0., 0., 0., 0., 1., 0., 0., 1.,],\n",
    "     [0., 0., 1., 1., 0., 0., 0., 0., 0., 0.,],\n",
    "     [0., 1., 0., 0., 0., 0., 0., 0., 1., 0.,],\n",
    "     [0., 1., 0., 0., 0., 0., 0., 0., 0., 1.,],\n",
    "     [0., 0., 0., 0., 0., 0., 0., 0., 1., 1.,],\n",
    "     [0., 0., 0., 0., 0., 0., 0., 1., 1., 0.,],\n",
    "     [1., 0., 0., 0., 0., 0., 0., 1., 0., 0.,],\n",
    "     [0., 0., 0., 0., 0., 1., 1., 0., 0., 0.,],\n",
    "     [0., 0., 1., 0., 1., 1., 0., 0., 0., 1.,],\n",
    "     [1., 0., 0., 1., 1., 0., 0., 0., 1., 0.,]]\n",
    ")\n",
    "ans_test_corpus_words = sorted([START_TOKEN, \"All\", \"ends\", \"that\", \"gold\", \"All's\", \"glitters\", \"isn't\", \"well\", END_TOKEN])\n",
    "word2ind_ans = dict(zip(ans_test_corpus_words, range(len(ans_test_corpus_words))))\n",
    "\n",
    "# Test correct word2ind\n",
    "assert (word2ind_ans == word2ind_test), \"Your word2ind is incorrect:\\nCorrect: {}\\nYours: {}\".format(word2ind_ans, word2ind_test)\n",
    "\n",
    "# Test correct M shape\n",
    "assert (M_test.shape == M_test_ans.shape), \"M matrix has incorrect shape.\\nCorrect: {}\\nYours: {}\".format(M_test.shape, M_test_ans.shape)\n",
    "\n",
    "# Test correct M values\n",
    "for w1 in word2ind_ans.keys():\n",
    "    idx1 = word2ind_ans[w1]\n",
    "    for w2 in word2ind_ans.keys():\n",
    "        idx2 = word2ind_ans[w2]\n",
    "        student = M_test[idx1, idx2]\n",
    "        correct = M_test_ans[idx1, idx2]\n",
    "        if student != correct:\n",
    "            print(\"Correct M:\")\n",
    "            print(M_test_ans)\n",
    "            print(\"Your M: \")\n",
    "            print(M_test)\n",
    "            raise AssertionError(\"Incorrect count at index ({}, {})=({}, {}) in matrix M. Yours has {} but should have {}.\".format(idx1, idx2, w1, w2, student, correct))\n",
    "\n",
    "# Print Success\n",
    "print (\"-\" * 80)\n",
    "print(\"Passed All Tests!\")\n",
    "print (\"-\" * 80)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "K-nyJnAASSt9",
    "jp-MarkdownHeadingCollapsed": true
   },
   "source": [
    "### Question 1.3: Implement `reduce_to_k_dim` [code] (1 point)\n",
    "\n",
    "Construct a method that performs dimensionality reduction on the matrix to produce k-dimensional embeddings. Use SVD to take the top k components and produce a new matrix of k-dimensional embeddings. \n",
    "\n",
    "**Note:** All of numpy, scipy, and scikit-learn (`sklearn`) provide *some* implementation of SVD, but only scipy and sklearn provide an implementation of Truncated SVD, and only sklearn provides an efficient randomized algorithm for calculating large-scale Truncated SVD. So please use [sklearn.decomposition.TruncatedSVD](https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.TruncatedSVD.html)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "id": "truGMjifSSt9"
   },
   "outputs": [],
   "source": [
    "def reduce_to_k_dim(M, k=2):\n",
    "    \"\"\" Reduce a co-occurence count matrix of dimensionality (num_corpus_words, num_corpus_words)\n",
    "        to a matrix of dimensionality (num_corpus_words, k) using the following SVD function from Scikit-Learn:\n",
    "            - http://scikit-learn.org/stable/modules/generated/sklearn.decomposition.TruncatedSVD.html\n",
    "    \n",
    "        Params:\n",
    "            M (numpy matrix of shape (number of unique words in the corpus , number of unique words in the corpus)): co-occurence matrix of word counts\n",
    "            k (int): embedding size of each word after dimension reduction\n",
    "        Return:\n",
    "            M_reduced (numpy matrix of shape (number of corpus words, k)): matrix of k-dimensioal word embeddings.\n",
    "                    In terms of the SVD from math class, this actually returns U * S\n",
    "    \"\"\"    \n",
    "    n_iters = 10    # Use this parameter in your call to `TruncatedSVD`\n",
    "    M_reduced = None\n",
    "    print(\"Running Truncated SVD over %i words...\" % (M.shape[0]))\n",
    "    \n",
    "    # ------------------\n",
    "    # Write your implementation here.\n",
    "    svd = TruncatedSVD(n_components=k, n_iter=n_iters)\n",
    "    M_reduced = svd.fit_transform(M)\n",
    "    \n",
    "    # ------------------\n",
    "\n",
    "    print(\"Done.\")\n",
    "    return M_reduced"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "7pqKuqvhSSt-",
    "outputId": "7d147bd2-9916-4226-d936-43ce795006cb"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Running Truncated SVD over 10 words...\n",
      "Done.\n",
      "--------------------------------------------------------------------------------\n",
      "Passed All Tests!\n",
      "--------------------------------------------------------------------------------\n"
     ]
    }
   ],
   "source": [
    "# ---------------------\n",
    "# Run this sanity check\n",
    "# Note that this is not an exhaustive check for correctness \n",
    "# In fact we only check that your M_reduced has the right dimensions.\n",
    "# ---------------------\n",
    "\n",
    "# Define toy corpus and run student code\n",
    "test_corpus = [\"{} All that glitters isn't gold {}\".format(START_TOKEN, END_TOKEN).split(\" \"), \"{} All's well that ends well {}\".format(START_TOKEN, END_TOKEN).split(\" \")]\n",
    "M_test, word2ind_test = compute_co_occurrence_matrix(test_corpus, window_size=1)\n",
    "M_test_reduced = reduce_to_k_dim(M_test, k=2)\n",
    "\n",
    "# Test proper dimensions\n",
    "assert (M_test_reduced.shape[0] == 10), \"M_reduced has {} rows; should have {}\".format(M_test_reduced.shape[0], 10)\n",
    "assert (M_test_reduced.shape[1] == 2), \"M_reduced has {} columns; should have {}\".format(M_test_reduced.shape[1], 2)\n",
    "\n",
    "# Print Success\n",
    "print (\"-\" * 80)\n",
    "print(\"Passed All Tests!\")\n",
    "print (\"-\" * 80)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "apZknsLoSSt_",
    "jp-MarkdownHeadingCollapsed": true
   },
   "source": [
    "### Question 1.4: Implement `plot_embeddings` [code] (1 point)\n",
    "\n",
    "Here you will write a function to plot a set of 2D vectors in 2D space. For graphs, we will use Matplotlib (`plt`).\n",
    "\n",
    "For this example, you may find it useful to adapt [this code](http://web.archive.org/web/20190924160434/https://www.pythonmembers.club/2018/05/08/matplotlib-scatter-plot-annotate-set-text-at-label-each-point/). In the future, a good way to make a plot is to look at [the Matplotlib gallery](https://matplotlib.org/gallery/index.html), find a plot that looks somewhat like what you want, and adapt the code they give."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {
    "id": "dDcCZE5lSSuA"
   },
   "outputs": [],
   "source": [
    "def plot_embeddings(M_reduced, word2ind, words):\n",
    "    \"\"\" Plot in a scatterplot the embeddings of the words specified in the list \"words\".\n",
    "        NOTE: do not plot all the words listed in M_reduced / word2ind.\n",
    "        Include a label next to each point.\n",
    "        \n",
    "        Params:\n",
    "            M_reduced (numpy matrix of shape (number of unique words in the corpus , 2)): matrix of 2-dimensioal word embeddings\n",
    "            word2ind (dict): dictionary that maps word to indices for matrix M\n",
    "            words (list of strings): words whose embeddings we want to visualize\n",
    "    \"\"\"\n",
    "\n",
    "    # ------------------\n",
    "    # Write your implementation here.\n",
    "    for word in words:\n",
    "        index = word2ind[word]\n",
    "        x, y = M_reduced[index, 0], M_reduced[index, 1]\n",
    "        plt.scatter(x, y, marker='x', color='red')\n",
    "        plt.text(x+0.01, y+0.01, word, fontsize=9)\n",
    "    plt.show()\n",
    "    \n",
    "    # ------------------"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 372
    },
    "id": "gHxOMWPxSSuB",
    "outputId": "565711fc-02ea-43cb-daa5-8b8bb310cdda"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--------------------------------------------------------------------------------\n",
      "Outputted Plot:\n"
     ]
    },
    {
     "data": {
      "image/png": "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D055eXlas2aN6x+a+rSk+VsbHx8fxcTE1PhYYmZmZp1jGRcXV6P++++/r0GDBsnb27veOi1hnjZEY8ZXqn7iNHnyZL3++uu6+uqrT/k6xhjl5OS02Hlal8aO78mys7Pdxo75W+10xnf9+vX69ttva/1e9Mla6/xtjHP6/fdsr1DRGu3du9dkZ2ebWbNmmY4dO5rs7GyTnZ1tysrKXHV69uxp3nzzTdf+3Llzjd1uN2+++abZuXOnufHGG2tdqrxz585mzZo1Zvv27eaKK65otUs9X3zxxSYrK8tkZWWZfv361Vjq+eTxNcYYh8Nh2rdvb55//vkabX777bdm1qxZ5tNPPzW7d+827777romOjjYDBgxgfE8xvmVlZebuu+82GzduNLt37zZr1641cXFx5oILLmD+1qKh43vkyBEzbtw407lzZ5OTk+O2PG5FRYUxpvXO3+NLEaenp5svv/zSpKammg4dOrhWx5oxY4ZJTk521T++VO6dd95pvvzyS5Oenl5jqdxPPvnEeHl5mblz55qvvvrKzJ07t1ksldsUGjq+r7/+umnbtq157rnn6lwyf+bMmWb16tVm165dJjs72/zxj380bdu2NZs3bz7r/WtqDR3fBQsWmIyMDPPNN9+Yzz//3MyYMcNIMitXrnTVYf7+qqHje9zNN99shgwZUmubzN9flZWVue5vJZknnnjCZGdnu1aBbUnvv4Sns2DSpElGUo1t7dq1rjqSzCuvvOLar6qqMn/7299MWFiY8fX1NcOGDTM7d+50a/eXX34xt912mwkKCjLt2rUzY8eONfn5+WepV83HwYMHzcSJE42/v7/x9/c3EydOrLF068nja4wx//znP027du1q/e2b/Px8M2zYMBMUFGR8fHzMhRdeaO64444av1XUGjR0fH/++WeTkJBgzj//fOPt7W26dOliJk2aVGNuMn+rNXR8d+/eXev7yYnvKa15/j733HOma9euxsfHxwwcONCsX7/edWzSpElm+PDhbvXXrVtnBgwYYHx8fEy3bt1q/Y8pK1asMD179jTe3t4mOjra7ea0tWnI+A4fPrzWeTpp0iRXndTUVNOlSxfj4+Njzj//fJOQkGA2btx4FnvUvDRkfOfNm2cuvPBC4+fnZ8477zzz//7f/zPvvvtujTaZv79q6PtDaWmpadeunXnxxRdrbY/5+6vjS+fX9f/3lvT+azPm/76dBQAAAACoE995AgAAAAALCE8AAAAAYAHhCQAAAAAsIDwBAAAAgAWEJwAAAACwgPAEAAAAABYQngAAAADAAsITAAAAAFhAeAIAAAAACwhPAAAAAGAB4QkAAAAALPj/CjmATmcAzWIAAAAASUVORK5CYII=",
      "text/plain": [
       "<Figure size 1000x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--------------------------------------------------------------------------------\n"
     ]
    }
   ],
   "source": [
    "# ---------------------\n",
    "# Run this sanity check\n",
    "# Note that this is not an exhaustive check for correctness.\n",
    "# The plot produced should look like the included file question_1.4_test.png \n",
    "# ---------------------\n",
    "\n",
    "print (\"-\" * 80)\n",
    "print (\"Outputted Plot:\")\n",
    "\n",
    "M_reduced_plot_test = np.array([[1, 1], [-1, -1], [1, -1], [-1, 1], [0, 0]])\n",
    "word2ind_plot_test = {'test1': 0, 'test2': 1, 'test3': 2, 'test4': 3, 'test5': 4}\n",
    "words = ['test1', 'test2', 'test3', 'test4', 'test5']\n",
    "plot_embeddings(M_reduced_plot_test, word2ind_plot_test, words)\n",
    "\n",
    "print (\"-\" * 80)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "YpBzYs2hSSuC",
    "jp-MarkdownHeadingCollapsed": true
   },
   "source": [
    "### Question 1.5: Co-Occurrence Plot Analysis [written] (3 points)\n",
    "\n",
    "Now we will put together all the parts you have written! We will compute the co-occurrence matrix with fixed window of 4 (the default window size), over the Large Movie Review corpus. Then we will use TruncatedSVD to compute 2-dimensional embeddings of each word. TruncatedSVD returns U\\*S, so we need to normalize the returned vectors, so that all the vectors will appear around the unit circle (therefore closeness is directional closeness). **Note**: The line of code below that does the normalizing uses the NumPy concept of *broadcasting*. If you don't know about broadcasting, check out\n",
    "[Computation on Arrays: Broadcasting by Jake VanderPlas](https://jakevdp.github.io/PythonDataScienceHandbook/02.05-computation-on-arrays-broadcasting.html).\n",
    "\n",
    "Run the below cell to produce the plot. It can take up to a few minutes to run."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 355
    },
    "id": "7L1Uk50mSSuD",
    "outputId": "35ae3e41-07c8-421b-e75c-dcbeaa4fb015"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Running Truncated SVD over 5880 words...\n",
      "Done.\n"
     ]
    },
    {
     "data": {
      "image/png": "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K/qc+2jZNmzYoAEDBqhp06Zyc3PTypUrf/eY9evXKzIyUj4+PoqIiNDcuXNL1eE+epkM1HpLly41PD09jTfffNPYuXOn8eijjxq+vr7GwYMHL1h/48aNRp06dYx//vOfxv79+42NGzcabdu2NQYNGuSoU1BQYHTu3Nm4/fbbja+++spITU01Nm7caKSkpFTWZVVrrujz9957z/D29jYWLVpkHDhwwPjss8+MJk2aGBMmTKisy6rWytvn8fHxhr+/v7F06VJj3759xpIlSww/Pz/j448/dtRJSkoy3N3djRkzZhi7du0yZsyYYXh4eBhbtmyprMuq9lzR7yNHjjRmz55t7Nixw9i1a5dxzz33GBaLxcjIyKisy6rWXNHnJVJTU42rrrrK6NGjh3HHHXe4+ErMwxV9zn304lzR59xHL2716tXGlClTjOXLlxuSjI8++uii9ffv32/Uq1fPePTRR42dO3cab775puHp6Wl8+OGHjjrcRy8fIQ5Gly5djHHjxjmVtW7d2njqqacuWP+ll14yIiIinMr+9a9/GaGhoY7Pc+bMMSIiIozCwsKKb3AN4Io+f+ihh4ybb77Zqc7EiRONG264oYJabW7l7fOoqCjj8ccfdyp79NFHje7duzs+Dx061OjTp49Tnd69exvDhw+voFabnyv6/Xxnz541/P39jQULFlx5g2sAV/X52bNnje7duxtvvfWWcffddxPizuGKPuc+enGu6HPuo5fuUkLcX//6V6N169ZOZQ888IDRrVs3x2fuo5ePxylrucLCQiUnJysmJsapPCYmRklJSRc8Jjo6WhkZGVq9erUMw9Dhw4f14Ycfql+/fo46H3/8saKiovTQQw8pJCRE7dq104wZM1RUVOTS6zEDV/X5DTfcoOTkZMdjZfv379fq1aud6tRWl9PnBQUF8vHxcSqrW7euvv76a505c0aStHnz5lLn7N27d5nnrG1c1e/nO3nypM6cOaMGDRpUTMNNzJV9Pm3aNDVq1Ehjx46t+IabmKv6nPto2VzV59xHK1ZZ98ht27ZxH60AhLha7tixYyoqKlJISIhTeUhIiLKzsy94THR0tBYtWqRhw4bJy8tLjRs3VmBgoF577TVHnf379+vDDz9UUVGRVq9eralTp+qVV17R9OnTXXo9ZuCqPh8+fLheeOEF3XDDDfL09FSLFi1000036amnnnLp9ZjB5fR579699dZbbyk5OVmGYWjbtm165513dObMGR07dkySlJ2dXa5z1jau6vfzPfXUU7rqqqt06623Vvg1mI2r+nzTpk16++239eabb7r8GszGVX3OfbRsrupz7qMVq6x75NmzZ7mPVgBCHCRJbm5uTp8NwyhVVmLnzp165JFH9Mwzzyg5OVlr1qzRgQMHNG7cOEed4uJiBQcHa968eYqMjNTw4cM1ZcoUzZkzx6XXYSYV3eeJiYmaPn264uPjtX37dq1YsUKrVq3SCy+84NLrMJPy9PnTTz+tvn37qlu3bvL09NQdd9yhMWPGSJLc3d0v65y1lSv6vcTf//53LVmyRCtWrCj1v+y1WUX2eV5enkaNGqU333xTQUFBrm66aVX0n3Puo7+vovuc+2jFu9Dv0fnl3EcvDyGulgsKCpK7u3up//E4cuRIqf8ZKREXF6fu3bvriSeeUIcOHdS7d2/Fx8frnXfeUVZWliSpSZMmatmypdM/utq0aaPs7GwVFha67oJMwFV9/vTTTys2Nlb33Xef2rdvrz/+8Y+aMWOG4uLiVFxc7PLrqs4up8/r1q2rd955RydPnlRqaqrS0tIUHh4uf39/xz9kGzduXK5z1jau6vcSL7/8smbMmKG1a9eqQ4cOLrsOM3FFn+/bt0+pqakaMGCAPDw85OHhoYULF+rjjz+Wh4eH9u3bVxmXVm256s8599GyuarPuY9WrLLukR4eHmrYsOFF63Af/X2EuFrOy8tLkZGRWrdunVP5unXrFB0dfcFjTp48qTp1nP/olNxkSv6HpXv37tq7d6/TX3o///yzmjRpIi8vr4q8BNNxVZ+XVcewT2BUUc03pcvp8xKenp4KDQ2Vu7u7li5dqv79+zv6OSoqqtQ5165d+7vnrC1c1e+S9NJLL+mFF17QmjVr1LlzZ5e034xc0eetW7fW999/r5SUFMc2cOBA3XTTTUpJSZHVanXlJVV7rvpzzn20bK7qc+6jFause2Tnzp3l6el50TrcRy9BJU6igmqqZJret99+29i5c6cxYcIEw9fX10hNTTUMwzCeeuopIzY21lH/3XffNTw8PIz4+Hhj3759xldffWV07tzZ6NKli6NOWlqa4efnZzz88MPG7t27jVWrVhnBwcHGiy++WOnXVx25os+fffZZw9/f31iyZImxf/9+Y+3atUaLFi2MoUOHVvr1VUfl7fPdu3cbCQkJxs8//2xs3brVGDZsmNGgQQPjwIEDjjqbNm0y3N3djZkzZxq7du0yZs6cydTI53FFv//tb38zvLy8jA8//NDIyspybHl5eZV9edWSK/r8fMxO6cwVfc599OJc0efcRy8uLy/P2LFjh7Fjxw5DkvHqq68aO3bscCzrcH6flywx8Nhjjxk7d+403n777VJLDHAfvXyEOBiGYRizZ882mjVrZnh5eRnXXXedsX79ese+u+++2+jZs6dT/X/961/GNddcY9StW9do0qSJcdddd5VaoykpKcno2rWr4e3tbURERBjTp083zp49WxmXYwoV3ednzpwxnnvuOaNFixaGj4+PYbVajfHjxxu//vprJV1R9VeePt+5c6fRqVMno27dukZAQIBxxx13GD/99FOpc37wwQdGq1atDE9PT6N169bG8uXLK+NSTKWi+71Zs2aGpFLbs88+W0lXVP254s/6uQhxpbmiz7mPXlxF9zn30Yv78ssvL/h37913320YxoX/7ZKYmGhce+21hpeXlxEeHm7MmTOn1Hm5j14eN8NgfBgAAAAAzIJ34gAAAADARAhxAAAAAGAihDgAAAAAMBFCHAAAAACYCCEOAAAAAEyEEAcAAAAAJkKIAwAAAAATIcQBAAAAgIkQ4gAAAADARAhxAAAAAGAihDgAAAAAMJH/B6yZUSHXE5lkAAAAAElFTkSuQmCC",
      "text/plain": [
       "<Figure size 1000x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# -----------------------------\n",
    "# Run This Cell to Produce Your Plot\n",
    "# ------------------------------\n",
    "imdb_corpus = read_corpus()\n",
    "M_co_occurrence, word2ind_co_occurrence = compute_co_occurrence_matrix(imdb_corpus)\n",
    "M_reduced_co_occurrence = reduce_to_k_dim(M_co_occurrence, k=2)\n",
    "\n",
    "# Rescale (normalize) the rows to make them each of unit-length\n",
    "M_lengths = np.linalg.norm(M_reduced_co_occurrence, axis=1)\n",
    "M_normalized = M_reduced_co_occurrence / M_lengths[:, np.newaxis] # broadcasting\n",
    "\n",
    "words = ['movie', 'book', 'mysterious', 'story', 'fascinating', 'good', 'interesting', 'large', 'massive', 'huge']\n",
    "\n",
    "plot_embeddings(M_normalized, word2ind_co_occurrence, words)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "RtOd40JTSSuE"
   },
   "source": [
    "**Verify that your figure matches \"question_1.5.png\" in the assignment zip. If not, use the figure in \"question_1.5.png\" to answer the next two questions.**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "ciXWGMvRSSuE"
   },
   "source": [
    "a. Find at least two groups of words that cluster together in 2-dimensional embedding space. Give an explanation for each cluster you observe."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "fLU7o3HESSuF"
   },
   "source": [
    "#### <font color=\"red\">book & movie, good & facinating. I think because book and movie represents cultural consumption product; and good and facinating both means approval.</font>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "3H-tyA9RSSuF"
   },
   "source": [
    "b. What doesn't cluster together that you might think should have? Describe at least two examples."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "3NG2j9J4SSuG"
   },
   "source": [
    "#### <font color=\"red\">massive & large & huge.</font>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "_h0OzAaRSSuI"
   },
   "source": [
    "## Part 2: Prediction-Based Word Vectors (15 points)\n",
    "\n",
    "As discussed in class, more recently prediction-based word vectors have demonstrated better performance, such as word2vec and GloVe (which also utilizes the benefit of counts). Here, we shall explore the embeddings produced by GloVe. Please revisit the class notes and lecture slides for more details on the word2vec and GloVe algorithms. If you're feeling adventurous, challenge yourself and try reading [GloVe's original paper](https://nlp.stanford.edu/pubs/glove.pdf).\n",
    "\n",
    "Then run the following cells to load the GloVe vectors into memory. **Note**: If this is your first time to run these cells, i.e. download the embedding model, it will take a couple minutes to run. If you've run these cells before, rerunning them will load the model without redownloading it, which will take about 1 to 2 minutes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "l3kwvdksSSuI",
    "outputId": "736a4672-4b59-4c2e-d6e6-b16e4548db30"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The history saving thread hit an unexpected error (OperationalError('attempt to write a readonly database')).History will not be written to the database.\n",
      "[==================================================] 100.0% 252.1/252.1MB downloaded\n",
      "Loaded vocab size 400000\n"
     ]
    }
   ],
   "source": [
    "def load_embedding_model():\n",
    "    \"\"\" Load GloVe Vectors\n",
    "        Return:\n",
    "            wv_from_bin: All 400000 embeddings, each length 200\n",
    "    \"\"\"\n",
    "    import gensim.downloader as api\n",
    "    wv_from_bin = api.load(\"glove-wiki-gigaword-200\")\n",
    "    print(\"Loaded vocab size %i\" % len(list(wv_from_bin.index_to_key)))\n",
    "    return wv_from_bin\n",
    "wv_from_bin = load_embedding_model()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "egIeG1RTSSuK"
   },
   "source": [
    "#### Note: If you are receiving a \"reset by peer\" error, rerun the cell to restart the download. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "rH9gWJWpSSuL"
   },
   "source": [
    "### Reducing dimensionality of Word Embeddings\n",
    "Let's directly compare the GloVe embeddings to those of the co-occurrence matrix. In order to avoid running out of memory, we will work with a sample of 40000 GloVe vectors instead.\n",
    "Run the following cells to:\n",
    "\n",
    "1. Put 40000 Glove vectors into a matrix M\n",
    "2. Run `reduce_to_k_dim` (your Truncated SVD function) to reduce the vectors from 200-dimensional to 2-dimensional."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {
    "id": "0PROUu_-SSuL"
   },
   "outputs": [],
   "source": [
    "def get_matrix_of_vectors(wv_from_bin, required_words):\n",
    "    \"\"\" Put the GloVe vectors into a matrix M.\n",
    "        Param:\n",
    "            wv_from_bin: KeyedVectors object; the 400000 GloVe vectors loaded from file\n",
    "        Return:\n",
    "            M: numpy matrix shape (num words, 200) containing the vectors\n",
    "            word2ind: dictionary mapping each word to its row number in M\n",
    "    \"\"\"\n",
    "    import random\n",
    "    words = list(wv_from_bin.index_to_key)\n",
    "    print(\"Shuffling words ...\")\n",
    "    random.seed(225)\n",
    "    random.shuffle(words)\n",
    "    print(\"Putting %i words into word2ind and matrix M...\" % len(words))\n",
    "    word2ind = {}\n",
    "    M = []\n",
    "    curInd = 0\n",
    "    for w in words:\n",
    "        try:\n",
    "            M.append(wv_from_bin.get_vector(w))\n",
    "            word2ind[w] = curInd\n",
    "            curInd += 1\n",
    "        except KeyError:\n",
    "            continue\n",
    "    for w in required_words:\n",
    "        if w in words:\n",
    "            continue\n",
    "        try:\n",
    "            M.append(wv_from_bin.get_vector(w))\n",
    "            word2ind[w] = curInd\n",
    "            curInd += 1\n",
    "        except KeyError:\n",
    "            continue\n",
    "    M = np.stack(M)\n",
    "    print(\"Done.\")\n",
    "    return M, word2ind"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "cpgM0M-hSSuM",
    "outputId": "b0989be9-8e11-45d8-8ba9-47297a990760"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Shuffling words ...\n",
      "Putting 400000 words into word2ind and matrix M...\n",
      "Done.\n",
      "Running Truncated SVD over 400000 words...\n",
      "Done.\n"
     ]
    }
   ],
   "source": [
    "# -----------------------------------------------------------------\n",
    "# Run Cell to Reduce 200-Dimensional Word Embeddings to k Dimensions\n",
    "# Note: This should be quick to run\n",
    "# -----------------------------------------------------------------\n",
    "M, word2ind = get_matrix_of_vectors(wv_from_bin, words)\n",
    "M_reduced = reduce_to_k_dim(M, k=2)\n",
    "\n",
    "# Rescale (normalize) the rows to make them each of unit-length\n",
    "M_lengths = np.linalg.norm(M_reduced, axis=1)\n",
    "M_reduced_normalized = M_reduced / M_lengths[:, np.newaxis] # broadcasting"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "8_46FYMJSSuN"
   },
   "source": [
    "**Note: If you are receiving out of memory issues on your local machine, try closing other applications to free more memory on your device. You may want to try restarting your machine so that you can free up extra memory. Then immediately run the jupyter notebook and see if you can load the word vectors properly. If you still have problems with loading the embeddings onto your local machine after this, please go to office hours or contact course staff.**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "zAFrU8ahSSuO"
   },
   "source": [
    "### Question 2.1: GloVe Plot Analysis [written] (3 points)\n",
    "\n",
    "Run the cell below to plot the 2D GloVe embeddings for `['movie', 'book', 'mysterious', 'story', 'fascinating', 'good', 'interesting', 'large', 'massive', 'huge']`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 321
    },
    "id": "9kHRkjz6SSuO",
    "outputId": "fbe0f8af-ea5e-456d-8d75-b9ad0caaa5fc",
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "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49XANmTIEO3bt0/PPvusdu7cqQ4dOujTTz9VixYtJLkWyj51TbbLLrvM/efVq1drzpw5atGihfLy8rzZVAAAAAAwHa+uw1Yb6sNaCwAAAADOXX3IBl6fJRIAAAAAUD0ENgAAAAAwKQIbAAAAAJgUgQ0AAAAATIrABgAAAAAmRWADAAAAcMGYOXOmunTpUtvNOGsENgAAAAA4S4Zh6Pjx4+ftfAQ2AAAAAPXSlClTJEkXXXSR4uLi9NJLL2nkyJH66aefFBISopCQEG3btk2GYejll19Wq1at1KhRI6WkpCg3N9d9nLi4OKWlpSkhIUENGzbUlClTFB8fr/JLWi9fvlwRERE6cuRIjV4DgQ0AAABAvfPrr7/queeekyRt375dK1asUJ8+fTRt2jR17NhRBw4c0IEDBxQbG6v09HT9/e9/14IFC7Rjxw61b99e/fv317Fjx9zHmzlzpt577z0dOHBAo0aN0uHDh7VkyRKP8tTUVAUFBdXodRDYAAAAANQ7vr6+7idghw8fVnR0tDp16lRp3fT0dD344IPq2LGjgoKC9Pzzz6ugoEArV6501xk1apTatGkjX19fBQQE6M4779TMmTMlSUeOHNGHH36ou+++u8avg8AGAAAAoN5p1aqV3njjDUnSJZdcouTkZGVnZ1dat6CgQHFxce7vgYGBat68uQoKCtzbYmNjPfb54x//qHnz5unAgQOaP3++YmJidMUVV9T4dRDYAAAAANRLAwcOlCRt3LhRnTt3lt1ul49PxQgUExOjvLw89/fS0lLt2LFDMTEx7m2n7temTRt17txZH330kWbOnKk//vGPXrkGAhsAAACAeicnJ0dff/21JCkgIEAhISHy8/NTdHS0du7cqcOHD7vrDhs2TFOmTNH69etVUlKip556ShdddJG6det2xnPcc889evnll/Xtt99q2LBhXrkOP68cFQAAAED95nBIxcVSuadQbgUFUmioZLWe/3b9T2lpqSZPnixJatmypTp37qyZM2fq0ksvVUJCgi666CKdOHFCP/74o+68807t2rVL/fv31/79+9WtWzf95z//kZ/fmePS4MGDNWbMGKWkpKhJkyZeuQ6LUX4uynrA6XTKarXK4XAoLCystpsDAAAA1D8Oh5SSIu3eLS1eLNlsJ8vy86VevaSoKCkjo1ZD2/nIBq1atdIrr7yiAQMGeOX4vBIJAAAAoGqKi11hLTfXFc7y813by8Jabq6rvLi4NlvpdXPnztWxY8fUr18/r52DVyIBAAAAVE1MjOvJWlk469VLSk+X7HbX9/h4V3llr0vWE+3atdNvv/2m9957T76+vl47D69EAgAAAKie8k/UypSFtfKvSdaS+pANeCUSAAAAQPXYbK4na+Wlp5sirNUXBDYAAAAA1ZOf73oNsjy7/eSYNpwzAhsAAACAqiv/OmR8vLRsmeufp05EgnNCYAMAAABQNQUFnmFt8WIpKcn1z/KhraCgdttZDzBLJAAAAICqCQ11rbMmeU4wYrOdnD0yKspVD+eEwAYAAACgaqxW16LYxcUVp+632aQlS1xhrRYXza4vCGwAAAAAqs5qPX0gq8frr51vjGEDAAAAAJMisAEAAACASRHYAAAAAMCkCGwAAAAAYFIENgAAAAAwKQIbAAAAAJgUgQ0AAAAATIrABgAAAAAmRWADAAAAAJMisAEAAACASRHYAAAAAMCkCGwAAAAAYFIENgAAAAAwKa8HtqlTp6ply5YKCgpS165dtXTp0jPWX7Jkibp27aqgoCDFx8dr2rRp3m4iAAAAAJiSVwPbBx98oIceekjjx4/X2rVrdfXVV6tv377atm1bpfW3bNmiG2+8UVdffbXWrl2rJ598Ug8++KDmzZvnzWYCAAAAgClZDMMwvHXw7t276/LLL9cbb7zh3tauXTvdcsstSktLq1D/8ccf1yeffKINGza4t40cOVI//PCDli9fflbndDqdslqtcjgcCgsLO/eLAAAAAFAn1Yds4LUnbKWlpVq9erWSk5M9ticnJyszM7PSfZYvX16hfp8+fbRq1SodPXq00n1KSkrkdDo9PgAAAABwvvXt21dTp06t0WP61ejRytm7d6+OHz+u6Ohoj+3R0dEqLCysdJ/CwsJK6x87dkx79+5Vs2bNKuyTlpamZ555puYaDgAAAADV8Nlnn9X4Mb0+6YjFYvH4bhhGhW2/V7+y7WXGjRsnh8Ph/uTn559jiwEAAADAHLwW2CIjI+Xr61vhadru3bsrPEUr07Rp00rr+/n5qXHjxpXuExgYqLCwMI8PAAAAAJTp2LGj0tLSdOWVVyo4OFh9+/bVb7/9pgceeEDh4eG65JJL3MO2iouLdd9996lZs2Zq1qyZRo4cqYMHD0qSBgwYoGeffdbj2KNGjdLIkSMlSb169dKrr77qLluzZo2uvfZaNWrUSBdffLGmT59e5bZ7LbAFBASoa9euWrRokcf2RYsWKSkpqdJ9EhMTK9T/4osvdMUVV8jf399bTQUAAABQz73//vuaN2+etm/frm3btqlbt27q3bu39u3bp6FDh7pD15gxY7Rp0yatW7dOP/30k3755ReNHTtWknTnnXcqPT3dfczS0lJ9+OGHstvtFc5XWFioG264QaNGjdKePXu0YMECTZw4UV999VWV2u3VVyIffvhhvf3225oxY4Y2bNigsWPHatu2be7OGDdunO688053/ZEjR2rr1q16+OGHtWHDBs2YMUPvvPOOHnnkEW82EwAAAEA998ADDyg2Nlbh4eHq16+fIiMjddttt8nX11e333671q1bp9LSUs2ZM0dpaWlq3LixIiMj9fzzz2vWrFk6ceKEbrrpJu3bt09ZWVmSpP/+97+KiIhQjx49KpwvPT1d11xzjQYPHixfX1916NBBd999t+bMmVOldntt0hFJGjJkiPbt26dnn31WO3fuVIcOHfTpp5+qRYsWkqSdO3d6rMnWsmVLffrppxo7dqxef/11NW/eXP/85z/1hz/8wZvNBAAAAFDPNW3a1P3nhg0bVvhuGIb279+vkpISxcXFucvi4+NVUlKivXv3KioqSoMHD9asWbOUkJCgWbNmVfp0TZLy8vL06aefKjw83L3t+PHjuvrqq6vUbq8GNsmVZB944IFKy2bOnFlhW8+ePbVmzRovtwoAAAAAPPn7+ysgIEB5eXnueTe2bNmiwMBARUZGSpLsdrsGDBigp59+Wp999pn+9re/VXosm82mW2+9VXPnzj2nNnl9lkgAAAAAqAt8fHyUmpqq8ePH67ffftO+ffs0fvx42e12+fi4olOPHj0UERGh4cOH64orrlCrVq0qPZbdbtfXX3+tefPm6ejRozp69Kiys7P1/fffV61N53xVAAAAAC5sDodUUFB5WUGBq7yO+Mc//qG4uDhdeumlat++vS6++GL9/e9/96hjt9v1+eefe8zHcaqLLrpIn3/+ud588001a9ZM0dHR+n//7//J6XRWqT0Wo2yhs3rC6XTKarXK4XAwxT8AAADgbQ6HlJIi7d4tLV4s2Wwny/LzpV69pKgoKSNDslrPa9PqQzbgCRsAAACA6isudoW13FxXOMvPd20vC2u5ua7y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CgkJ0TPPPKMbbrhBTZo0UUREhPr166e8vDx3/UWLFqlTp04KDQ1VdHS0Ro0a5S7buHGjBgwYoCZNmqhRo0YaOHCgJCkvL08Wi0VFRUWSpOHDh2vEiBEaOnSoQkND1aZNGy1evNh9nNmzZ6tDhw4KDQ1VbGysJkyYIMMwJEndunWTJCUlJSkkJETPP/98lY9fVFSkQYMGKTw8XG3bttVrr70mi8VS850LAAAA1DAC2wXm3//+t+644w498MADOnDggO666y49/PDDys/P19atW9WwYUONGDHCXf+uu+7So48+quLiYuXm5sput0uSDh48qOuvv14dOnRQXl6eCgsL9ac//em05507d67uu+8+FRUVyW63a/jw4e6yRo0a6eOPP5bT6dQnn3yit956S3PmzJEkrVy5UpKUmZmpAwcO6Mknn6zy8f/0pz/p4MGD2rp1q7755hulp6dXt/sAAACA84rAdoGLi4tT3759FRQUpLCwMI0fP17ffvutTpw4IUny9/fXpk2btGfPHgUHByspKUmStHDhQvn7+2vy5MkKDg5WQECArr322tOep1+/furdu7d8fX119913a+vWrdq3b58kqW/fvmrdurUsFou6dOmi22+/3eMJ2dk43fGPHz+uDz74QM8++6ysVquaNWumRx99tHqdBQAAAJxnBLYL3J49e5SamiqbzaawsDBdc801Ki0tVXFxsSRp/vz5Wrdundq0aaPLLrtMH374oSRp69atatWq1Vm/Wti0aVP3n4ODgyXJfY7PP/9cSUlJioyMlNVq1bRp07R3794qXcfpjr93714dPXpUNpvNXR4bG1ulYwMAAAC1xWuBbf/+/bLb7bJarbJarbLb7e4xR2eyYcMGDRgwQFarVaGhoUpISNC2bdu81cwL3rhx43To0CGtWbNGTqdT3377rSS5x5Bdfvnlmjdvnvbu3asJEyYoNTVVu3btUosWLbR582Z3veoqLS3VwIEDdf/992v79u1yOBwaOXKkx3HPZbxZZGSk/P39lZ+f797G7wkAAAB1hdcCW2pqqrKzs5WRkaGMjAxlZ2e7xz+dzubNm3XVVVepbdu2Wrx4sX744QdNmDBBQUFB3mrmBc/pdKphw4YKDw/Xvn379Mwzz7jLSktLlZ6erv3798vHx0fh4eGSJD8/P/Xr108lJSV6+umndfDgQZWWluqbb76p8vlLSkp05MgRNW7cWIGBgVqxYoV7/FqZ6Ohobd68uVrX5+vrq8GDB2vSpElyOp0qLCzUyy+/XK1jAQAAAOebVwLbhg0blJGRobfffluJiYlKTEzU9OnTtXDhQuXk5Jx2v/Hjx+vGG2/Uiy++qMsuu0zx8fHq16+foqKivNFM73E4pIKCyssKClzlJvHMM89o06ZNioiIUI8ePdS3b1+P8jlz5ujiiy9WaGio/vSnP2nOnDlq3LixQkJC9OWXX2r16tWKjY1Vs2bN9Prrr1f5/KGhoXr99dd13333KSwsTJMnT9aQIUM86vzlL3/Rgw8+qIiICL3wwgtVPsdrr72mwMBA2Ww29erVS4MHD1ZAQECVjwMAAACcbxbjXN9pq8SMGTP08MMPV3gFMjw8XK+88oruvvvuCvucOHFCVqtVjz32mL777jutXbtWLVu21Lhx43TLLbec9lwlJSUqKSlxf3c6nbLZbHI4HAoLC6upSzp7DoeUkiLt3i0tXiyVGzul/HypVy8pKkrKyJCs1vPfPmjOnDl6+umntWnTptpuCgAAALzI6XTKarXWXjaoAV55wlZYWFjpU7GoqCgVFhZWus/u3bt14MABvfDCC0pJSdEXX3yhW2+9VQMHDtSSJUtOe660tDT3ODmr1eoxuUStKC52hbXcXFc4Kxs7VRbWcnNd5f+bcAPet3HjRq1atUqGYWjjxo2aPHmyBg0aVNvNAgAAAH5XlQLbpEmTZLFYzvhZtWqVpMonijAM47QTSJRNI3/zzTdr7Nix6tKli5544gn1799f06ZNO22bxo0bJ4fD4f6Un1yiVsTEuJ6sxcefDG2ZmSfDWny8qzwmpnbbeQE5ePCghg0bppCQEPXs2VM9e/bUU089VdvNAgAAAH6XX1Uqjx49WkOHDj1jnbi4OP3444/atWtXhbI9e/YoOjq60v0iIyPl5+enSy+91GN7u3bt9N133532fIGBgQoMDDyL1p9HNpsrlJWFtB49XNvLwlptPwW8wHTp0kW//PJLbTcDAAAAqLIqBbbIyEhFRkb+br3ExEQ5HA6tXLlS3bp1kyStWLFCDofDvfDyqQICAnTllVdWmJTk119/VYsWLarSTHOw2aT09JNhTXJ9J6wBAAAAOEteGcPWrl07paSkaMSIEcrKylJWVpZGjBih/v37q02bNu56bdu21fz5893fH330UX3wwQeaPn26Nm3apClTpug///mPHnjgAW8007vy86VTlzGw20+OaQMAAACA3+G1ddhmz56tjh07Kjk5WcnJyerUqZPS09M96uTk5MhRbor7W2+9VdOmTdOLL76ojh076u2339a8efN01VVXeauZ3lF+gpH4eGnZMs8xbYQ2AAAAAGfBK9P616Zan7qzoEDq2dNzghGbrWKIW7KEiUcAAAAAL6r1bFADqjSGDWchNNS1zprkOcFI+YlIoqJc9QAAAADgDAhsNc1qdS2KXVxc8QmazeZ6shYayqLZAAAAAH4Xgc0brNbTBzJegwQAAABwlrw26QgAAAAA4NwQ2AAAAADApAhsAAAAAGBSBDYAAAAAMCkCGwAAAACYFIENAAAAAEyKwAYAAAAAJkVgAwAAAACTIrABAAAAgEn51XYDapphGJIkp9NZyy0BAAAAUJvKMkFZRqiL6l1gKy4uliTZbLZabgkAAAAAMyguLpbVaq3tZlSLxajLcbMSJ06c0I4dOxQaGiqLxVLbzakWp9Mpm82m/Px8hYWF1XZz6hX61jvoV++hb72DfvUe+tY76FfvoW+9wyz9ahiGiouL1bx5c/n41M3RYPXuCZuPj49iYmJquxk1IiwsjBuHl9C33kG/eg996x30q/fQt95Bv3oPfesdZujXuvpkrUzdjJkAAAAAcAEgsAEAAACASRHYTCgwMFATJ05UYGBgbTel3qFvvYN+9R761jvoV++hb72DfvUe+tY76NeaU+8mHQEAAACA+oInbAAAAABgUgQ2AAAAADApAhsAAAAAmBSBDQAAAABMisAGAAAAACZFYPOCqVOnqmXLlgoKClLXrl21dOnSM9afPXu2OnfurIYNG6pZs2a6++67tW/fPo868+bN06WXXqrAwEBdeumlmj9//jmfty6q6b6dPn26rr76akVERCgiIkLXX3+9Vq5c6XGMSZMmyWKxeHyaNm3qleurLTXdrzNnzqzQZxaLRUeOHDmn89ZFNd23vXr1qrRv+/Xr567Db7ai119/Xe3atVODBg3Upk0bzZo1q0Id7rMuNd233Gddarpfuc+eVNN9y31W+vbbb3XTTTepefPmslgsWrBgwe/us2TJEnXt2lVBQUGKj4/XtGnTKtThPltNBmrU3LlzDX9/f2P69OnG+vXrjTFjxhjBwcHG1q1bK62/dOlSw8fHx/jH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",
      "text/plain": [
       "<Figure size 1000x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "words = ['movie', 'book', 'mysterious', 'story', 'fascinating', 'good', 'interesting', 'large', 'massive', 'huge']\n",
    "\n",
    "plot_embeddings(M_reduced_normalized, word2ind, words)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Verify that your figure matches \"question_2.1.png\" in the assignment zip. If not, use the figure in \"question_2.1.png\" (and the figure in \"question_1.5.png\", if applicable) to answer the next two questions.**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "eOnrMZkzSSuP"
   },
   "source": [
    "a. What is one way the plot is different from the one generated earlier from the co-occurrence matrix? What is one way it's similar?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "5KC4PTQoSSuQ"
   },
   "source": [
    "#### <font color=\"red\">Write your answer here.</font>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "bNDY5puZSSuQ"
   },
   "source": [
    "b. Why might the GloVe plot (question_2.1.png) differ from the plot generated earlier from the co-occurrence matrix (question_1.5.png)?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "o-cWAvi8SSuR"
   },
   "source": [
    "#### <font color=\"red\">Write your answer here.</font>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "nA8oIbjjSSuS"
   },
   "source": [
    "### Cosine Similarity\n",
    "Now that we have word vectors, we need a way to quantify the similarity between individual words, according to these vectors. One such metric is cosine-similarity. We will be using this to find words that are \"close\" and \"far\" from one another.\n",
    "\n",
    "We can think of n-dimensional vectors as points in n-dimensional space. If we take this perspective [L1](http://mathworld.wolfram.com/L1-Norm.html) and [L2](http://mathworld.wolfram.com/L2-Norm.html) Distances help quantify the amount of space \"we must travel\" to get between these two points. Another approach is to examine the angle between two vectors. From trigonometry we know that:\n",
    "\n",
    "<img src=\"./imgs/inner_product.png\" width=20% style=\"float: center;\"></img>\n",
    "\n",
    "Instead of computing the actual angle, we can leave the similarity in terms of $similarity = cos(\\Theta)$. Formally the [Cosine Similarity](https://en.wikipedia.org/wiki/Cosine_similarity) $s$ between two vectors $p$ and $q$ is defined as:\n",
    "\n",
    "$$s = \\frac{p \\cdot q}{||p|| ||q||}, \\textrm{ where } s \\in [-1, 1] $$ "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "sFfCOLUsSSuS",
    "jp-MarkdownHeadingCollapsed": true
   },
   "source": [
    "### Question 2.2: Words with Multiple Meanings (1.5 points) [code + written] \n",
    "Polysemes and homonyms are words that have more than one meaning (see this [wiki page](https://en.wikipedia.org/wiki/Polysemy) to learn more about the difference between polysemes and homonyms ). Find a word with *at least two different meanings* such that the top-10 most similar words (according to cosine similarity) contain related words from *both* meanings. For example, \"leaves\" has both \"go_away\" and \"a_structure_of_a_plant\" meaning in the top 10, and \"scoop\" has both \"handed_waffle_cone\" and \"lowdown\". You will probably need to try several polysemous or homonymic words before you find one. \n",
    "\n",
    "Please state the word you discover and the multiple meanings that occur in the top 10. Why do you think many of the polysemous or homonymic words you tried didn't work (i.e. the top-10 most similar words only contain **one** of the meanings of the words)?\n",
    "\n",
    "**Note**: You should use the `wv_from_bin.most_similar(word)` function to get the top 10 most similar words. This function ranks all other words in the vocabulary with respect to their cosine similarity to the given word. For further assistance, please check the __[GenSim documentation](https://radimrehurek.com/gensim/models/keyedvectors.html#gensim.models.keyedvectors.FastTextKeyedVectors.most_similar)__."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "ZAr09U-xSSuT",
    "outputId": "da8adff7-c61e-43a0-8f4b-66084b4a66b8"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Checking word: bank\n",
      "Top 10 most similar words to 'bank':\n",
      "banks: 0.7625692486763\n",
      "banking: 0.6818838715553284\n",
      "central: 0.6283640265464783\n",
      "financial: 0.6166563034057617\n",
      "credit: 0.6049751043319702\n",
      "lending: 0.5980609059333801\n",
      "monetary: 0.596300482749939\n",
      "bankers: 0.5913100838661194\n",
      "loans: 0.5802940726280212\n",
      "investment: 0.5740202069282532\n",
      "\n",
      "Checking word: leaves\n",
      "Top 10 most similar words to 'leaves':\n",
      "ends: 0.6128068566322327\n",
      "leaf: 0.6027014851570129\n",
      "stems: 0.5998532176017761\n",
      "takes: 0.5902854204177856\n",
      "leaving: 0.5761634111404419\n",
      "grows: 0.5663397312164307\n",
      "flowers: 0.5600922107696533\n",
      "turns: 0.5536050796508789\n",
      "leave: 0.5496847629547119\n",
      "goes: 0.5434924960136414\n",
      "\n",
      "Checking word: bat\n",
      "Top 10 most similar words to 'bat':\n",
      "bats: 0.6917243599891663\n",
      "batting: 0.616058886051178\n",
      "balls: 0.5692732930183411\n",
      "batted: 0.5530908107757568\n",
      "toss: 0.5506129264831543\n",
      "wicket: 0.5495278835296631\n",
      "pitch: 0.5489361882209778\n",
      "bowled: 0.5452010035514832\n",
      "hitter: 0.5353438258171082\n",
      "batsman: 0.5348091125488281\n",
      "\n",
      "Checking word: scoop\n",
      "Top 10 most similar words to 'scoop':\n",
      "scoops: 0.6437130570411682\n",
      "spoon: 0.5459856390953064\n",
      "scooped: 0.5319252610206604\n",
      "slice: 0.5201053023338318\n",
      "innside: 0.5154464244842529\n",
      "cream: 0.4963829219341278\n",
      "scooping: 0.4923388659954071\n",
      "buckets: 0.4825914800167084\n",
      "fatman: 0.4815135896205902\n",
      "scrape: 0.47744491696357727\n",
      "\n",
      "Checking word: light\n",
      "Top 10 most similar words to 'light':\n",
      "bright: 0.6242775321006775\n",
      "dark: 0.6141002774238586\n",
      "lights: 0.6013951897621155\n",
      "lighter: 0.558175265789032\n",
      "heavy: 0.5408364534378052\n",
      "sunlight: 0.5362918972969055\n",
      "blue: 0.5349380970001221\n",
      "colored: 0.5282374620437622\n",
      "sky: 0.5239452719688416\n",
      "color: 0.5139290690422058\n",
      "\n",
      "Checking word: spring\n",
      "Top 10 most similar words to 'spring':\n",
      "summer: 0.8025314807891846\n",
      "autumn: 0.7510946989059448\n",
      "winter: 0.7315691113471985\n",
      "fall: 0.6582663655281067\n",
      "beginning: 0.6507853865623474\n",
      "starting: 0.6281814575195312\n",
      "year: 0.6142007112503052\n",
      "start: 0.5800089836120605\n",
      "next: 0.577118456363678\n",
      "during: 0.5726782083511353\n"
     ]
    }
   ],
   "source": [
    "# ------------------\n",
    "# Write your implementation here.\n",
    "import gensim\n",
    "\n",
    "# 加载词向量模型\n",
    "\n",
    "def find_polysemes_and_homonyms(word):\n",
    "    try:\n",
    "        # 获取前10个最相似的单词\n",
    "        similar_words = wv_from_bin.most_similar(word, topn=10)\n",
    "        \n",
    "        # 输出结果\n",
    "        print(f\"Top 10 most similar words to '{word}':\")\n",
    "        for similar_word, similarity in similar_words:\n",
    "            print(f\"{similar_word}: {similarity}\")\n",
    "    \n",
    "    except KeyError:\n",
    "        print(f\"Word '{word}' not in vocabulary.\")\n",
    "\n",
    "# 需要手动尝试几个具有多重意义的单词\n",
    "words_to_try = ['bank', 'leaves', 'bat', 'scoop', 'light', 'spring']\n",
    "\n",
    "for word in words_to_try:\n",
    "    print(f\"\\nChecking word: {word}\")\n",
    "    find_polysemes_and_homonyms(word)\n",
    "\n",
    "# ------------------"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "VdQ018tjSSuT"
   },
   "source": [
    "#### <font color=\"red\">Maybe bacause underfitting.</font>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "VfeW-eK9SSuU",
    "jp-MarkdownHeadingCollapsed": true
   },
   "source": [
    "### Question 2.3: Synonyms & Antonyms (2 points) [code + written] \n",
    "\n",
    "When considering Cosine Similarity, it's often more convenient to think of Cosine Distance, which is simply 1 - Cosine Similarity.\n",
    "\n",
    "Find three words $(w_1,w_2,w_3)$ where $w_1$ and $w_2$ are synonyms and $w_1$ and $w_3$ are antonyms, but Cosine Distance $(w_1,w_3) <$ Cosine Distance $(w_1,w_2)$. \n",
    "\n",
    "As an example, $w_1$=\"happy\" is closer to $w_3$=\"sad\" than to $w_2$=\"cheerful\". Please find a different example that satisfies the above. Once you have found your example, please give a possible explanation for why this counter-intuitive result may have happened.\n",
    "\n",
    "You should use the the `wv_from_bin.distance(w1, w2)` function here in order to compute the cosine distance between two words. Please see the __[GenSim documentation](https://radimrehurek.com/gensim/models/keyedvectors.html#gensim.models.keyedvectors.FastTextKeyedVectors.distance)__ for further assistance."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "bwlpPjpHSSuV",
    "outputId": "8c983677-b3d1-4423-d31c-da566cb522a5"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Checking words: happy, sad, cheerful\n",
      "Cosine Distance between 'happy' and 'sad' (synonyms): 0.40401363372802734\n",
      "Cosine Distance between 'happy' and 'cheerful' (antonyms): 0.517246663570404\n",
      "'happy' is closer to 'sad' than to 'cheerful'\n",
      "\n",
      "Checking words: good, bad, nice\n",
      "Cosine Distance between 'good' and 'bad' (synonyms): 0.28903746604919434\n",
      "Cosine Distance between 'good' and 'nice' (antonyms): 0.3369309902191162\n",
      "'good' is closer to 'bad' than to 'nice'\n",
      "\n",
      "Checking words: strong, weak, robust\n",
      "Cosine Distance between 'strong' and 'weak' (synonyms): 0.33029669523239136\n",
      "Cosine Distance between 'strong' and 'robust' (antonyms): 0.34200507402420044\n",
      "'strong' is closer to 'weak' than to 'robust'\n",
      "\n",
      "Checking words: high, low, tall\n",
      "Cosine Distance between 'high' and 'low' (synonyms): 0.23716121912002563\n",
      "Cosine Distance between 'high' and 'tall' (antonyms): 0.6528714001178741\n",
      "'high' is closer to 'low' than to 'tall'\n"
     ]
    }
   ],
   "source": [
    "# ------------------\n",
    "# Write your implementation here.\n",
    "def find_counter_intuitive_example(word1, word2, word3):\n",
    "    try:\n",
    "        # 计算余弦距离\n",
    "        dist_12 = wv_from_bin.distance(word1, word2)\n",
    "        dist_13 = wv_from_bin.distance(word1, word3)\n",
    "        \n",
    "        print(f\"Cosine Distance between '{word1}' and '{word2}' (synonyms): {dist_12}\")\n",
    "        print(f\"Cosine Distance between '{word1}' and '{word3}' (antonyms): {dist_13}\")\n",
    "        \n",
    "        # 检查条件\n",
    "        if dist_12 < dist_13:\n",
    "            print(f\"'{word1}' is closer to '{word2}' than to '{word3}'\")\n",
    "        else:\n",
    "            print(f\"'{word1}' is not closer to '{word2}' than to '{word3}'\")\n",
    "    \n",
    "    except KeyError as e:\n",
    "        print(e)\n",
    "\n",
    "# 尝试这些词汇组合\n",
    "word_pairs = [\n",
    "    ('happy', 'sad', 'cheerful'),\n",
    "    ('good', 'bad', 'nice'),\n",
    "    ('strong', 'weak', 'robust'),\n",
    "    ('high', 'low', 'tall')\n",
    "]\n",
    "\n",
    "for word1, word2, word3 in word_pairs:\n",
    "    print(f\"\\nChecking words: {word1}, {word2}, {word3}\")\n",
    "    find_counter_intuitive_example(word1, word2, word3)\n",
    "\n",
    "# ------------------"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "PeIHjTFMSSuV"
   },
   "source": [
    "#### <font color=\"red\">1. Underfitting and insufficient training   2. The data set is not large enough.  3. Angle may be small, but the distance is far.</font>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "ZxIDq26zSSuW"
   },
   "source": [
    "### Question 2.4: Analogies with Word Vectors [written] (1.5 points)\n",
    "Word vectors have been shown to *sometimes* exhibit the ability to solve analogies. \n",
    "\n",
    "As an example, for the analogy \"man : grandfather :: woman : x\" (read: man is to grandfather as woman is to x), what is x?\n",
    "\n",
    "In the cell below, we show you how to use word vectors to find x using the `most_similar` function from the __[GenSim documentation](https://radimrehurek.com/gensim/models/keyedvectors.html#gensim.models.keyedvectors.KeyedVectors.most_similar)__. The function finds words that are most similar to the words in the `positive` list and most dissimilar from the words in the `negative` list (while omitting the input words, which are often the most similar; see [this paper](https://www.aclweb.org/anthology/N18-2039.pdf)). The answer to the analogy will have the highest cosine similarity (largest returned numerical value)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "u0pC7H4VSSuY",
    "outputId": "a2e3a0c1-2621-4def-f00b-f3de583f86bf"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[('grandmother', 0.7608444690704346),\n",
      " ('granddaughter', 0.7200808525085449),\n",
      " ('daughter', 0.7168302536010742),\n",
      " ('mother', 0.715153694152832),\n",
      " ('niece', 0.7005683183670044),\n",
      " ('father', 0.6659888029098511),\n",
      " ('aunt', 0.6623408794403076),\n",
      " ('grandson', 0.6618767380714417),\n",
      " ('grandparents', 0.6446609497070312),\n",
      " ('wife', 0.6445354223251343)]\n"
     ]
    }
   ],
   "source": [
    "# Run this cell to answer the analogy -- man : grandfather :: woman : x\n",
    "pprint.pprint(wv_from_bin.most_similar(positive=['woman', 'grandfather'], negative=['man']))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "XVv8I9WwSSuZ"
   },
   "source": [
    "Let $m$, $g$, $w$, and $x$ denote the word vectors for `man`, `grandfather`, `woman`, and the answer, respectively. Using **only** vectors $m$, $g$, $w$, and the vector arithmetic operators $+$ and $-$ in your answer, what is the expression in which we are maximizing cosine similarity with $x$?\n",
    "\n",
    "Hint: Recall that word vectors are simply multi-dimensional vectors that represent a word. It might help to draw out a 2D example using arbitrary locations of each vector. Where would `man` and `woman` lie in the coordinate plane relative to `grandfather` and the answer?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "KlUKBqtHSSuZ"
   },
   "source": [
    "#### <font color=\"red\">x = g - m + w.</font>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "2rRgMca9SSua"
   },
   "source": [
    "### Question 2.5: Finding Analogies [code + written]  (1.5 points)\n",
    "a. For the previous example, it's clear that \"grandmother\" completes the analogy. But give an intuitive explanation as to why the `most_similar` function gives us words like \"granddaughter\", \"daughter\", or \"mother?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "WgYQXazQSSua"
   },
   "source": [
    "#### <font color=\"red\">Because they are most closest words to grandmother.</font>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "p9aAUXEISSub"
   },
   "source": [
    "b. Find an example of analogy that holds according to these vectors (i.e. the intended word is ranked top). In your solution please state the full analogy in the form x:y :: a:b. If you believe the analogy is complicated, explain why the analogy holds in one or two sentences.\n",
    "\n",
    "**Note**: You may have to try many analogies to find one that works!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {
    "id": "CRvYK2xifpq7"
   },
   "outputs": [],
   "source": [
    "# For example: x, y, a, b = (\"\", \"\", \"\", \"\")\n",
    "# ------------------\n",
    "# Write your implementation here.\n",
    "x, y, a, b = (\"man\", \"woman\", \"actor\", \"actress\")\n",
    "\n",
    "# ------------------\n",
    "\n",
    "# Test the solution\n",
    "assert wv_from_bin.most_similar(positive=[a, y], negative=[x])[0][0] == b"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "K3QlPqAwSSub"
   },
   "source": [
    "#### <font color=\"red\">Similar to the provided example.</font>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "SwgcEywwSSuc"
   },
   "source": [
    "### Question 2.6: Incorrect Analogy [code + written] (1.5 points)\n",
    "a. Below, we expect to see the intended analogy \"hand : glove :: foot : **sock**\", but we see an unexpected result instead. Give a potential reason as to why this particular analogy turned out the way it did?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "m-ykWoJoSSuc",
    "outputId": "60fa3812-3e62-429e-c309-349463c75f9e"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[('45,000-square', 0.4922032058238983),\n",
      " ('15,000-square', 0.4649604260921478),\n",
      " ('10,000-square', 0.45447564125061035),\n",
      " ('6,000-square', 0.44975781440734863),\n",
      " ('3,500-square', 0.4441334903240204),\n",
      " ('700-square', 0.442575067281723),\n",
      " ('50,000-square', 0.4356396794319153),\n",
      " ('3,000-square', 0.43486514687538147),\n",
      " ('30,000-square', 0.43305960297584534),\n",
      " ('footed', 0.43236881494522095)]\n"
     ]
    }
   ],
   "source": [
    "pprint.pprint(wv_from_bin.most_similar(positive=['foot', 'glove'], negative=['hand']))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "zn4ruS8MSSud"
   },
   "source": [
    "#### <font color=\"red\">Bad dataset or underfitting.</font>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "D1gHyZt0SSud"
   },
   "source": [
    "b. Find another example of analogy that does *not* hold according to these vectors. In your solution, state the intended analogy in the form x:y :: a:b, and state the **incorrect** value of b according to the word vectors (in the previous example, this would be **'45,000-square'**)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {
    "id": "ms-DTC8_ftiA"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[('airplane', 0.5633475184440613),\n",
      " ('crash', 0.5420618057250977),\n",
      " ('jet', 0.5302508473396301),\n",
      " ('planes', 0.5221787691116333),\n",
      " ('aircraft', 0.5217697024345398),\n",
      " ('crashed', 0.50178062915802),\n",
      " ('jetliner', 0.4972861111164093),\n",
      " ('boeing', 0.4941837191581726),\n",
      " ('flight', 0.4868796467781067),\n",
      " ('airliner', 0.479977548122406)]\n"
     ]
    }
   ],
   "source": [
    "# For example: x, y, a, b = (\"\", \"\", \"\", \"\")\n",
    "# ------------------\n",
    "# Write your implementation here.\n",
    "x, y, a, b = (\"bicycle\", \"tire\", \"plane\", \"wheel\")\n",
    "\n",
    "# ------------------\n",
    "pprint.pprint(wv_from_bin.most_similar(positive=[a, y], negative=[x]))\n",
    "assert wv_from_bin.most_similar(positive=[a, y], negative=[x])[0][0] != b"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "c4x0EHjeSSue"
   },
   "source": [
    "#### <font color=\"red\">Write your answer here.</font>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "yvlycXN-SSuf"
   },
   "source": [
    "### Question 2.7: Guided Analysis of Bias in Word Vectors [written] (1 point)\n",
    "\n",
    "It's important to be cognizant of the biases (gender, race, sexual orientation etc.) implicit in our word embeddings. Bias can be dangerous because it can reinforce stereotypes through applications that employ these models.\n",
    "\n",
    "Run the cell below, to examine (a) which terms are most similar to \"man\" and \"profession\" and most dissimilar to \"woman\" and (b) which terms are most similar to \"woman\" and \"profession\" and most dissimilar to \"man\". Point out the difference between the list of female-associated words and the list of male-associated words, and explain how it is reflecting gender bias."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "XggWA4MhSSuf",
    "outputId": "534a694b-f4fa-479e-9e7c-12b17db3abb8"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[('reputation', 0.5250176191329956),\n",
      " ('professions', 0.5178037881851196),\n",
      " ('skill', 0.49046963453292847),\n",
      " ('skills', 0.4900550842285156),\n",
      " ('ethic', 0.48976603150367737),\n",
      " ('business', 0.4875852167606354),\n",
      " ('respected', 0.4859202802181244),\n",
      " ('practice', 0.482104629278183),\n",
      " ('regarded', 0.4778572916984558),\n",
      " ('life', 0.4760662317276001)]\n",
      "\n",
      "[('professions', 0.5957456231117249),\n",
      " ('practitioner', 0.4988412857055664),\n",
      " ('teaching', 0.48292145133018494),\n",
      " ('nursing', 0.48211798071861267),\n",
      " ('vocation', 0.4788966476917267),\n",
      " ('teacher', 0.4716033935546875),\n",
      " ('practicing', 0.46937811374664307),\n",
      " ('educator', 0.46524307131767273),\n",
      " ('physicians', 0.4628995954990387),\n",
      " ('professionals', 0.4601394832134247)]\n"
     ]
    }
   ],
   "source": [
    "# Run this cell\n",
    "# Here `positive` indicates the list of words to be similar to and `negative` indicates the list of words to be\n",
    "# most dissimilar from.\n",
    "\n",
    "pprint.pprint(wv_from_bin.most_similar(positive=['man', 'profession'], negative=['woman']))\n",
    "print()\n",
    "pprint.pprint(wv_from_bin.most_similar(positive=['woman', 'profession'], negative=['man']))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "x4g6KbsYSSuh"
   },
   "source": [
    "#### <font color=\"red\">First list focuses on reputation and high qualities, while the second are mostly associated with low-level skills and low-paid jobs. It looks the model indeed learned stereotypes that men are more likely to own those high qualities than woman, which is not true.</font>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "LxJmnS6lSSui"
   },
   "source": [
    "### Question 2.8: Independent Analysis of Bias in Word Vectors [code + written]  (1 point)\n",
    "\n",
    "Use the `most_similar` function to find another pair of analogies that demonstrates some bias is exhibited by the vectors. Please briefly explain the example of bias that you discover."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "PZoDheIfSSui",
    "outputId": "f45fef83-ee36-4ef1-b970-775c3b40c515"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[('undocumented', 0.5947777032852173),\n",
      " ('mexico', 0.5630810856819153),\n",
      " ('immigrant', 0.5471600294113159),\n",
      " ('farmworkers', 0.537945568561554),\n",
      " ('migrants', 0.5257075428962708),\n",
      " ('tijuana', 0.5210056900978088),\n",
      " ('juarez', 0.5172142386436462),\n",
      " ('chiapas', 0.5051199793815613),\n",
      " ('mexicans', 0.4953947067260742),\n",
      " ('immigrants', 0.48942831158638)]\n",
      "[('china', 0.6159747838973999),\n",
      " ('beijing', 0.5495448112487793),\n",
      " ('laborers', 0.5263511538505554),\n",
      " ('guangdong', 0.5094733834266663),\n",
      " ('mainland', 0.5093905925750732),\n",
      " ('migrants', 0.5041223764419556),\n",
      " ('zhejiang', 0.4969698488712311),\n",
      " ('taiwanese', 0.4943785071372986),\n",
      " ('zhang', 0.48823249340057373),\n",
      " ('labourers', 0.4878397583961487)]\n"
     ]
    }
   ],
   "source": [
    "# ------------------\n",
    "# Write your implementation here.\n",
    "pprint.pprint(wv_from_bin.most_similar(positive=['mexican', 'migrant'], negative=['chinese']))\n",
    "pprint.pprint(wv_from_bin.most_similar(positive=['chinese', 'migrant'], negative=['mexican']))\n",
    "\n",
    "# ------------------"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "TGOlmtJoSSuj"
   },
   "source": [
    "#### <font color=\"red\">Mainly chinese-associated words are about territory.</font>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "eK2XVWzmSSuk"
   },
   "source": [
    "### Question 2.9: Thinking About Bias [written] (2 points)\n",
    "\n",
    "a. Give one explanation of how bias gets into the word vectors. Briefly describe a real-world example that demonstrates this source of bias. Your real-world example should be focused on word vectors, as opposed to bias in other AI systems (e.g., ChatGPT)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "19pM85fCSSuk"
   },
   "source": [
    "#### <font color=\"red\">If training dataset containing much biases, and trainning without supervision or inteference, then the trained model is assured borned with much biases. I think it's because the men produced this dataset know almost nothing about chinese, and know many mexicans are undocumented and work as farmers.</font>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "ILYqJZ7ASSul"
   },
   "source": [
    "b. What is one method you can use to mitigate bias exhibited by word vectors? Briefly describe a real-world example that demonstrates this method."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "dnJaAB7mSSul"
   },
   "source": [
    "\n",
    "#### <font color=\"red\">Maybe a way to mitigate this is to get rid of words like \"king\", \"queen\", \"actor\" & \"actress. It's about preprocessing.</font>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "dzh3eEmZSSum"
   },
   "source": [
    "# <font color=\"blue\"> Submission Instructions</font>\n",
    "\n",
    "1. Click the Save button at the top of the Jupyter Notebook.\n",
    "2. Select Cell -> All Output -> Clear. This will clear all the outputs from all cells (but will keep the content of all cells). \n",
    "2. Select Cell -> Run All. This will run all the cells in order, and will take several minutes.\n",
    "3. Once you've rerun everything, select File -> Download as -> PDF via LaTeX (If you have trouble using \"PDF via LaTex\", you can also save the webpage as pdf. <font color='blue'> Make sure all your solutions especially the coding parts are displayed in the pdf</font>, it's okay if the provided codes get cut off because lines are not wrapped in code cells).\n",
    "4. Look at the PDF file and make sure all your solutions are there, displayed correctly. The PDF is the only thing your graders will see!\n",
    "5. Submit your PDF on Gradescope."
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "colab": {
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
